package search

import (
	b "code.google.com/p/gochess/board"
	pos "code.google.com/p/gochess/position"
	"math/rand"
)

type zobristHash uint64

// zobrist keys for transportation tables
var zobristMatrix [b.H8 - b.A1 + 1][pos.BiggestPiece + 1]zobristHash

var zobristEnPassantSquare [b.H6 + 1]zobristHash

var zobristCastlingRights [pos.NumberOfCastlingRights]zobristHash

// who is in turn in that particular position?
var zobristBlacksTurnHash = generateHash()

// see https://groups.google.com/forum/?fromgroups#!topic/golang-nuts/Kle874lT1Eo
func generateHash() zobristHash {
	return zobristHash(rand.Uint32())<<32 + zobristHash(rand.Uint32())

}

func init() {
	// fill zobrist matrix
	for i := range zobristMatrix {
		for j := range zobristMatrix[i] {
			zobristMatrix[i][pos.Piece(j)] = generateHash()
		}
	}
	for i := range zobristEnPassantSquare {
		zobristEnPassantSquare[i] = generateHash()
	}
	for i := range zobristCastlingRights {
		zobristCastlingRights[i] = generateHash()
	}
}

func computeHash(p *pos.Position) zobristHash {

	hash := zobristHash(0)
	for square := b.A1; square <= b.H8; square++ {
		piece := p.Get(square)
		if piece != pos.NoPiece && b.IsValid(square) {
			hash ^= zobristMatrix[square-b.A1][piece]
		}
	}

	hash ^= zobristEnPassantSquare[p.EnPassantTarget()]
	hash ^= zobristCastlingRights[p.CastlingRights()]

	if p.SideToMove() == pos.Black {
		hash ^= zobristBlacksTurnHash
	}
	return hash
}

// TODO: write tests for this
// todo: does stm matter here? if yes, we need to adjust position.SwitchSides()
func computePawnHash(p *pos.Position) zobristHash {

	hash := zobristHash(0)
	for color := pos.White; color <= pos.Black; color++ {
		pawn := pos.NewPiece(color, pos.Pawn)
		pawns := p.PieceList(pawn)
		for i := pawns.Max - 1; i >= 0; i-- {
			hash ^= zobristMatrix[pawns.Pieces[i]-b.A1][pawn]
		}
	}
	return hash
}

func (this *zobristHash) setPiece(piece pos.Piece, location b.Square) {
	*this ^= zobristMatrix[location-b.A1][piece]
}

func (this *zobristHash) removePiece(piece pos.Piece, location b.Square) {
	*this ^= zobristMatrix[location-b.A1][piece]
}

func (this *zobristHash) movePiece(piece pos.Piece, src, dst b.Square) {
	*this ^= zobristMatrix[src-b.A1][piece] ^ zobristMatrix[dst-b.A1][piece]
}

func (this *zobristHash) switchSides() {
	*this ^= zobristBlacksTurnHash
}

func (this *zobristHash) updateEnPassantTarget(current, new b.Square) {
	if current != new {
		*this ^= zobristEnPassantSquare[current] ^ zobristEnPassantSquare[new]
	}
}

func (this *zobristHash) updateCastlingRights(current, new pos.CastlingRights) {
	if current != new {
		*this ^= zobristCastlingRights[current] ^ zobristCastlingRights[new]
	}
}
